package com.kancy.stream.rabbit.config;

import com.kancy.stream.rabbit.properties.RabbitMessageProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * RabbitMessageChannelRegistrar
 *
 * @author kancy
 * @date 2020/3/7 19:59
 */
public class RabbitMessageChannelRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private static final Logger log = LoggerFactory.getLogger(RabbitMessageChannelRegistrar.class);

    private static final String QUEUE_NAME_FORMAT = "%s.%s";
    private static final String EXCHANGE_NAME_FORMAT = "%s.%s";

    private ApplicationContext context;

    private RabbitMessageProperties rabbitMessageProperties;

    private BeanDefinitionRegistry registry;
    
    /**
     * Register bean definitions as necessary based on the given annotation metadata of
     * the importing {@code @Configuration} class.
     *
     * @param importingClassMetadata annotation metadata of the importing class
     * @param registry               current bean definition registry
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BindResult<RabbitMessageProperties> messagePropertiesBindResult = Binder.get(context.getEnvironment()).bind("stream.rabbit", RabbitMessageProperties.class);
        if (messagePropertiesBindResult.isBound()){
            this.rabbitMessageProperties = messagePropertiesBindResult.get();
            this.registry = registry;
            // 注册交换机
            registerExchange();
            // 注册队列
            registerQueue();
            // 注册绑定
            registerBinding();
            // 注册绑定队列
            registerBindingQueue();
            log.info("Stream rabbit message queue register success.");
        }
    }

    /**
     * 注册绑定队列
     */
    private void registerBindingQueue() {
        Map<String, RabbitMessageProperties.BindingQueueProperties> bindingQueueMap = new HashMap<>();
        bindingQueueMap.putAll(rabbitMessageProperties.getBindingQueue());
        bindingQueueMap.entrySet().forEach(e ->{
            BeanDefinitionBuilder queueBeanDefinitionBuilder = initQueueBeanDefinitionBuilder(e.getKey(), e.getValue());
            registry.registerBeanDefinition(String.format("queue-%s", e.getKey()), queueBeanDefinitionBuilder.getRawBeanDefinition());
            log.debug("Register queue [{}] success.", e.getKey());
        });

        bindingQueueMap.entrySet().stream().forEach(e ->{
            BeanDefinitionBuilder queueBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Binding.class);
            RabbitMessageProperties.BindingQueueProperties bingProperties = e.getValue();
            queueBeanDefinitionBuilder.addConstructorArgValue(bingProperties.getName());
            queueBeanDefinitionBuilder.addConstructorArgValue(Binding.DestinationType.QUEUE);
            queueBeanDefinitionBuilder.addConstructorArgValue(bingProperties.getExchangeName());
            queueBeanDefinitionBuilder.addConstructorArgValue(bingProperties.getRoutingKey());
            queueBeanDefinitionBuilder.addConstructorArgValue(bingProperties.getArguments());
            registry.registerBeanDefinition(String.format("binding-%s", e.getKey()), queueBeanDefinitionBuilder.getRawBeanDefinition());
            log.debug("Register binding [{}] success: routingKey [{}], queue [{}], exchangeName [{}]",
                    e.getKey(), bingProperties.getRoutingKey(), bingProperties.getName(), bingProperties.getExchangeName());
        });
    }

    /**
     * 注册绑定
     */
    private void registerBinding() {
        rabbitMessageProperties.getBinding().entrySet().stream().forEach(e ->{
            BeanDefinitionBuilder queueBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Binding.class);
            RabbitMessageProperties.BingProperties bingProperties = e.getValue();
            queueBeanDefinitionBuilder.addConstructorArgValue(bingProperties.getDestination());
            queueBeanDefinitionBuilder.addConstructorArgValue(bingProperties.getDestinationType());
            queueBeanDefinitionBuilder.addConstructorArgValue(bingProperties.getExchangeName());
            queueBeanDefinitionBuilder.addConstructorArgValue(bingProperties.getRoutingKey());
            queueBeanDefinitionBuilder.addConstructorArgValue(bingProperties.getArguments());
            registry.registerBeanDefinition(String.format("binding-%s", e.getKey()), queueBeanDefinitionBuilder.getRawBeanDefinition());
            log.debug("Register binding [{}] success: routingKey [{}], destination [{}], exchangeName [{}]",
                    e.getKey(), bingProperties.getRoutingKey(), bingProperties.getDestination(), bingProperties.getExchangeName());
        });
    }

    /**
     * 注册队列
     */
    private void registerQueue() {
        rabbitMessageProperties.getQueue().entrySet().stream().forEach(e ->{
            BeanDefinitionBuilder queueBeanDefinitionBuilder = initQueueBeanDefinitionBuilder(e.getKey(), e.getValue());
            registry.registerBeanDefinition(String.format("queue-%s", e.getKey()), queueBeanDefinitionBuilder.getRawBeanDefinition());
            log.debug("Register queue [{}] success.", e.getKey());
        });
    }

    private BeanDefinitionBuilder initQueueBeanDefinitionBuilder(String queueKey, RabbitMessageProperties.QueueProperties queueProperties) {
        BeanDefinitionBuilder queueBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Queue.class);
        queueBeanDefinitionBuilder.addConstructorArgValue(getQueueName(queueKey, queueProperties));
        queueBeanDefinitionBuilder.addConstructorArgValue(queueProperties.isDurable());
        queueBeanDefinitionBuilder.addConstructorArgValue(queueProperties.isExclusive());
        queueBeanDefinitionBuilder.addConstructorArgValue(queueProperties.isAutoDelete());

        // 添加死信相关属性
        Map<String, Object> arguments = queueProperties.getArguments();
        if (StringUtils.hasText(queueProperties.getDlxExchangeName())){
            arguments.put(RabbitMessageProperties.QueueProperties.DLX_EXCHANGE_NAME, queueProperties.getDlxExchangeName());
        }
        if (StringUtils.hasText(queueProperties.getDlxRoutingKey())){
            arguments.put(RabbitMessageProperties.QueueProperties.DLX_ROUTING_KEY, queueProperties.getDlxRoutingKey());
        }
        if (Objects.nonNull(queueProperties.getTtl()) && queueProperties.getTtl().toMillis() > 0){
            arguments.put(RabbitMessageProperties.QueueProperties.X_MESSAGE_TTL, queueProperties.getTtl().toMillis());
        }
        queueBeanDefinitionBuilder.addConstructorArgValue(arguments);
        return queueBeanDefinitionBuilder;
    }

    /**
     * 注册交换机
     */
    private void registerExchange() {
        rabbitMessageProperties.getExchange().entrySet().stream().forEach(e ->{
            BeanDefinitionBuilder directExchangeBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CustomExchange.class);
            RabbitMessageProperties.ExchangeProperties exchangeProperties = e.getValue();
            directExchangeBeanDefinitionBuilder.addConstructorArgValue(getExchangeName(rabbitMessageProperties.getProject(), e.getKey(), exchangeProperties.getName()));
            directExchangeBeanDefinitionBuilder.addConstructorArgValue(exchangeProperties.getType());
            directExchangeBeanDefinitionBuilder.addConstructorArgValue(exchangeProperties.isDurable());
            directExchangeBeanDefinitionBuilder.addConstructorArgValue(exchangeProperties.isAutoDelete());
            directExchangeBeanDefinitionBuilder.addConstructorArgValue(exchangeProperties.getArguments());
            registry.registerBeanDefinition(String.format("exchange-%s", e.getKey()), directExchangeBeanDefinitionBuilder.getRawBeanDefinition());
            log.debug("Register exchange [{}] success.", e.getKey());
        });
    }


    /**
     * 获取队列名称
     * @param queueKey
     * @param queueProperties
     * @return
     */
    private String getQueueName(String queueKey, RabbitMessageProperties.QueueProperties queueProperties) {
        if (StringUtils.hasText(queueProperties.getName())){
            return queueProperties.getName();
        }
        if (queueProperties instanceof RabbitMessageProperties.BindingQueueProperties){
            RabbitMessageProperties.BindingQueueProperties bindingQueueProperties = (RabbitMessageProperties.BindingQueueProperties) queueProperties;
            String exchangeName = bindingQueueProperties.getExchangeName();
            return String.format(QUEUE_NAME_FORMAT, exchangeName, queueKey);
        }
        return String.format(QUEUE_NAME_FORMAT, rabbitMessageProperties.getProject(), queueKey);
    }

    /**
     * 获取交换机名称
     * @param project
     * @param exchangeKey
     * @param exchangeName
     * @return
     */
    private String getExchangeName(String project, String exchangeKey, String exchangeName) {
        if (StringUtils.hasText(exchangeName)){
            return exchangeName;
        }
        return String.format(EXCHANGE_NAME_FORMAT, project, exchangeKey);
    }


    /**
     * Set the ResourceLoader that this object runs in.
     *
     * @param resourceLoader the ResourceLoader object to be used by this object
     * @see ResourcePatternResolver
     * @see ResourcePatternUtils#getResourcePatternResolver
     */
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.context = (ApplicationContext) resourceLoader;
    }
}
